/*
 * This file is part of ELCube.
 *
 * ELCube is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ELCube is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ELCube.  If not, see <https://www.gnu.org/licenses/>.
 */
package cn.nkpro.elcube.security;

import cn.nkpro.elcube.exception.NkAccessDeniedException;
import cn.nkpro.elcube.security.bo.GrantedAuthority;
import cn.nkpro.elcube.security.bo.UserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;

@SuppressWarnings("all")
@Component
public class NkSecurityRunner {

    @Qualifier("nkAccountService")
    @Autowired@SuppressWarnings("all")
    private UserAccountService userDetailsService;

    /**
     * 用指定用户 以超级权限 来运行指定函数
     * @param username
     * @param function
     */
    public <T> T returnAsUser(@NotNull String username, @NotNull ReturnableFunction function){
        return returnAsUser(username,true,function);
    }
    /**
     * 用指定用户 来运行指定函数
     * @param username
     * @param superAuthority
     * @param function
     */
    public <T> T returnAsUser(@NotNull String username, Boolean superAuthority, @NotNull ReturnableFunction function){
        UserDetails details = userDetailsService.loadUserByUsernameFromCache(username);
        if(details==null){
            throw new NkAccessDeniedException(username + "不存在");
        }
        return returnAsUser(details,superAuthority,function);
    }

    /**
     * 用指定用户 以超级权限 来运行指定函数
     * @param username
     * @param function
     */
    public <T> T returnAsUser(@NotNull UserDetails details, Boolean superAuthority, @NotNull ReturnableFunction function){

        Authentication currAuthentication = SecurityContextHolder.getContext().getAuthentication();
        try{
            List<GrantedAuthority> authorities = details.getAuthorities();
            if(superAuthority){
                GrantedAuthority authority = new GrantedAuthority();
                authority.setAuthority("*:*");
                authorities = Collections.singletonList(authority);
            }

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                    details.getUsername(),
                    null,
                    authorities
            );
            authentication.setDetails(details);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            return (T) function.apply();
        }finally {
            SecurityContextHolder.clearContext();
            if(currAuthentication!=null)
                SecurityContextHolder.getContext().setAuthentication(currAuthentication);
        }
    }

    /**
     * 用指定用户 以超级权限 来运行指定函数
     *
     * 不推荐使用
     * @see #runAsUser(String, ReturnableFunction)
     * @param username
     * @param function
     */
    @Deprecated
    public void runAsUser(@NotNull String username, @NotNull Function function){
        Authentication currAuthentication = SecurityContextHolder.getContext().getAuthentication();
        try{
            UserDetails details = userDetailsService.loadUserByUsernameFromCache(username);

            GrantedAuthority authority = new GrantedAuthority();
            authority.setAuthority("*:*");

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                    details.getUsername(),
                    null,
                    Collections.singletonList(authority)
            );
            authentication.setDetails(details);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            function.apply();
        }finally {
            SecurityContextHolder.clearContext();
            if(currAuthentication!=null)
                SecurityContextHolder.getContext().setAuthentication(currAuthentication);
        }
    }

    @FunctionalInterface
    public interface Function{
        void apply();
    }

    @FunctionalInterface
    public interface ReturnableFunction{
        Object apply();
    }
}
